package team.archai.nexus.boot.auth.util;

import cn.hutool.crypto.symmetric.AES;
import cn.hutool.crypto.symmetric.SymmetricAlgorithm;
import cn.hutool.json.JSONUtil;
import lombok.SneakyThrows;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.shiro.SecurityUtils;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.stereotype.Component;
import team.archai.nexus.boot.auth.constant.AuthConstant;
import team.archai.nexus.boot.auth.entity.AuthUserInfo;
import team.archai.nexus.boot.auth.entity.JwtParam;
import team.archai.nexus.boot.auth.exception.AuthException;
import team.archai.nexus.boot.auth.exception.ExpiredException;
import team.archai.nexus.boot.auth.exception.InvalidException;
import team.archai.nexus.boot.auth.model.AuthenticatedInfo;
import team.archai.nexus.boot.auth.support.IJwtHelper;
import team.archai.nexus.boot.auth.token.IAuth;
import team.archai.nexus.boot.auth.token.JwtAuth;
import team.archai.nexus.boot.auth.token.ShiroJwtAuth;
import team.archai.nexus.boot.cache.support.CacheHelper;
import team.archai.nexus.boot.tool.Tool;
import team.archai.nexus.boot.tool.WebUtil;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.nio.charset.StandardCharsets;
import java.util.*;

/**
 * @program: tiny-dust-boot-build
 * @description: 认证工具类
 * @author: lishibin
 * @create: 2020-06-27 14:12
 **/
@Component
@ConditionalOnClass(CacheHelper.class)
public class AuthUtil {

    private static final Log log = LogFactory.getLog(AuthUtil.class);

    @Resource
    protected IJwtHelper iJwtHelper;

    private static IJwtHelper jwtHelper;

    @Resource
    protected CacheHelper cacheHelper;

    protected static CacheHelper cacheUtil;

    private static final String USER_ATTR = "_USER_REQUEST_ATTR_";

    @PostConstruct
    public void init() {
        AuthUtil.jwtHelper = iJwtHelper;
        AuthUtil.cacheUtil = cacheHelper;
    }

    /**
     * @program: JwtHelper
     * @description: 登录 授权token   根据配置授权类型发起授权token
     * 授权方式------security 和 shiro
     * @author: lishibin
     * @create: 1:59 PM
     **/
    public static IAuth login(JwtParam jwtParam) {
        ShiroJwtAuth shiroJwtAuth = ShiroJwtAuth.builder().build();
        IAuth auth = initAuth(jwtParam);
        shiroJwtAuth.setToken(auth.getCredentials());
        shiroJwtAuth.setAuthenticatedInfo(auth.getPrincipal());
        SecurityUtils.getSubject().login(shiroJwtAuth);
        return shiroJwtAuth;
    }

    /**
     * @program: AuthUtil
     * @description: 初始化认证信息
     * @author: lishibin
     * @create: 21:18
     **/
    public static IAuth initAuth(JwtParam jwtParam) {
        AuthenticatedInfo authenticatedInfo = initPrincipal(jwtParam);
        JwtAuth jwtAuth = JwtAuth.builder().build();
        jwtAuth.setAuthenticatedInfo(authenticatedInfo);
        jwtAuth.setToken(authenticatedInfo.getToken());
        return jwtAuth;
    }

    /**
     * @program: AuthUtil
     * @description: 初始化认证信息
     * @author: lishibin
     * @create: 12:45 PM
     **/
    private static AuthenticatedInfo initPrincipal(JwtParam jwtParam) {
        log.info("login jwt token. jwtParam =>" + jwtParam.toString());
        String token = jwtHelper.create(jwtParam);
        log.info("login token. =>" + token);
        return AuthenticatedInfo.builder().credential(jwtParam.getCredential()).keyId(jwtParam.getSignKey()).expire(jwtParam.getExp())
                .subject(jwtParam.getSubject()).additionalJson(JSONUtil.toJsonStr(jwtParam.getAdditional())).token(token).build().encodeAdditionalJson();
    }

    /**
     * @program: AuthUtil
     * @description: 退出 撤销token认证
     * @author: lishibin
     * @create: 6:07 PM
     **/
    public static boolean logout(JwtParam jwtParam) {
        log.info("logout jwt token. jwtParam =>" + jwtParam.toString());
        jwtHelper.revoke(jwtParam);
        SecurityUtils.getSubject().logout();
        return true;
    }

    public static IAuth check(String token) {
        return jwtHelper.parse(token);
    }

    /**
     * @program: AuthUtil
     * @description: 解析token
     * @author: lishibin
     * @create: 12:45 PM
     **/
    public static IAuth parse(String token) throws ExpiredException, InvalidException {
        log.info("parse auth info. token =>" + token);
        AuthAssert.jwtParamNull(token, "Token is must not null.");
        String[] decodeHeaders = extractAndDecodeHeader();
        token = token.replace(decodeHeaders[0] + AuthConstant.BASIC_HEADER_SPLIT, "");
        return check(token);
    }

    /**
     * @program: AuthUtil
     * @description: 获取当前登录的用户信息
     * @author: lishibin
     * @create: 19:47
     **/
    public static AuthUserInfo authUserInfo() {
        HttpServletRequest request = WebUtil.getRequest();
        if (request == null) {
            return null;
        }
        // 优先从 request 中获取
        String token = WebUtil.getHeader(AuthConstant.TOKEN_HEADER);
        if (token == null) {
            return null;
        }
        AuthUserInfo authUserInfo = (AuthUserInfo) request.getAttribute(USER_ATTR);
        if (Tool.isEmpty(authUserInfo)) {
            IAuth authJson = parse(token);
            AuthenticatedInfo authenticatedInfo = authJson.getPrincipal();
            String additional = authenticatedInfo.decodeAdditionalJson();
            authUserInfo = JSONUtil.toBean(additional, AuthUserInfo.class);
            if (authUserInfo != null) {
                // 设置到 request 中
                request.setAttribute(USER_ATTR, authUserInfo);
            }
        }
        return authUserInfo;
    }


    /**
     * 解密 字符串使用 aes ecb 模式
     *
     * @param encrypted
     * @param secret
     * @return
     */
    public static String decryptEcb(String encrypted, String secret) {
        byte[] key = cn.hutool.crypto.SecureUtil.generateKey(SymmetricAlgorithm.AES.getValue(), cn.hutool.crypto.SecureUtil.md5(secret).getBytes(StandardCharsets.UTF_8)).getEncoded();
        AES aes = cn.hutool.crypto.SecureUtil.aes(key);
        String decrypted;
        try {
            decrypted = new String(aes.decrypt(encrypted), StandardCharsets.UTF_8);
        } catch (Exception e) {
            e.printStackTrace();
            return "";
        }
        return decrypted;
    }

    /**
     * 加密 字符串使用aes ecb模式
     *
     * @param decrypted
     * @param secret
     * @return
     */
    public static String encryptEcb(String decrypted, String secret) {
        byte[] key = cn.hutool.crypto.SecureUtil.generateKey(SymmetricAlgorithm.AES.getValue(), cn.hutool.crypto.SecureUtil.md5(secret).getBytes(StandardCharsets.UTF_8)).getEncoded();
        AES aes = cn.hutool.crypto.SecureUtil.aes(key);
        String encrypted;
        try {
            encrypted = aes.encryptHex(decrypted);
        } catch (Exception e) {
            e.printStackTrace();
            return "";
        }
        return encrypted;
    }

    /**
     * 客户端信息解码
     * [0] signVersion
     * [1] clientId
     * [2] clientSecret
     */
    @SneakyThrows(Exception.class)
    public static String[] extractAndDecodeHeader() {
        // 获取请求头客户端信息
        String header = Objects.requireNonNull(WebUtil.getRequest()).getHeader(AuthConstant.BASIC_HEADER_KEY);
        List<String> headers = Tool.toStrList(AuthConstant.BASIC_HEADER_SPLIT, header);
        if (headers.size() != 2) {
            throw new AuthException("No Authorization Version Info.");
        }
        Optional<String> opt = Arrays.stream(AuthConstant.AUTH_VERSIONS).filter(r -> r.equals(headers.get(0))).findFirst();
        if (!opt.isPresent()) {
            throw new AuthException("Unknown Authorization Version.");
        }
        byte[] base64Token = headers.get(1).getBytes(StandardCharsets.UTF_8);

        byte[] decoded;
        try {
            decoded = Base64.getDecoder().decode(base64Token);
        } catch (IllegalArgumentException var7) {
            throw new RuntimeException("Failed to decode basic authentication token");
        }

        String token = new String(decoded, StandardCharsets.UTF_8);
        int index = token.indexOf(AuthConstant.CLIENT_HEADER_SPLIT);
        if (index == -1) {
            throw new RuntimeException("Invalid basic authentication token");
        } else {
            return new String[]{opt.get(), token.substring(0, index), token.substring(index + 1)};
        }
    }

}
